package wikitools.model;

import java.util.ArrayList;
import java.util.List;

import wikitools.model.exception.ArticleNotFoundException;
import wikitools.model.exception.DuplicatedArticleIDException;
import wikitools.model.exception.DuplicatedNoteIDException;
import wikitools.model.exception.NoteNotFoundException;
import wikitools.util.Default;

import com.db4o.Db4o;
import com.db4o.ObjectContainer;
import com.db4o.query.Predicate;

public class TextBase {
    private ObjectContainer database;
    private IdManager idManager;
    private IdManager idNoteManager;

    private String datafile;

    public TextBase(String dbpath) {
        super();
        this.datafile = dbpath;
        this.idManager = this.getIdManager(Article.class);
        this.idNoteManager = this.getIdManager(Note.class);
    }

    private IdManager getIdManager(Class type) {
        this.open();
        try {
            return _getIdManager(type);
        } finally {
            this.close();
        }
    }

    private IdManager _getIdManager(Class type) {
        List<IdManager> idmans = this.database.query(this
                .matchIdManagerType(type));
//        System.out.println("Got " + idmans.size() + " idmans for " + type.toString());
        if (idmans.size() == 0) { // if none , create
            IdManager man = new IdManager(type);
            this.database.store(man);
            this.database.commit();
            return man;
        } else {
            return idmans.get(0);
        }
    }

    private Predicate<IdManager> matchIdManagerType(final Class type) {
        return new Predicate<IdManager>() {

            public boolean match(IdManager candidate) {
                return candidate.getCategory().equals(type);
            }
        };
    }

    private void _updateIdManager(Class type) {
        IdManager man = this._getIdManager(type);
        this.database.store(man);
        this.database.commit();
    }

    public TextBase() {
        this(Default.DBFILE);

    }

    private void open() {
        // TODO: detect the database's state to avoid re-opening
        this.database = Db4o.openFile(this.datafile);
    }

    private void close() {
        this.database.close();
    }

    private void commit() {
        this.database.commit();
    }

    public List<Article> getArticleList() {
        this.open();

        try {
            List<Article> list = this.database.query(Article.class);
            // copy from ObjectSet to an ArrayList so that data are not lazy.
            ArrayList<Article> x = new ArrayList<Article>(list);
            return x;
        } finally {
            this.close();
        }

    }

    public Article getArticleById(final String id) throws Exception {
        this.open();
        try {

            List<Article> list = this.database.query(matchId(id));
            if (list.size() == 0) {
                throw new ArticleNotFoundException();
            } else if (list.size() > 1) {
                this.database.delete(list.get(0));
                throw new DuplicatedArticleIDException();
            } else {
                Article x = list.get(0);
                return x;
            }

        } finally {
            this.close();
        }
    }
    
    public void updateArticle(Article item) throws Exception{
        this.open();
        try {

            List<Article> list = this.database.query(matchId(item.getId()));
            if (list.size() == 0) {
                throw new ArticleNotFoundException();
            } else if (list.size() > 1) {
                this.database.delete(list.get(0));
                throw new DuplicatedArticleIDException();
            } else {
//                this.database.delete(list.get(0));
                list.get(0).setPubDate(item.getPubDate());
                list.get(0).setAuthor(item.getAuthor());
                list.get(0).setContent(item.getContent());
                list.get(0).setId(item.getId());
                list.get(0).setTitle(item.getTitle());
                System.out.println(list.get(0).getPubDate());
                this.database.store(list.get(0));
                this.database.commit();
//                System.out.println(item);
//                this.close();
//                Article x = this.getArticleById(item.getId());
//                System.out.println("x: " + x);
            }

        } finally {
            this.close();
        }
    }
    
    
    public Note getNoteById(final String id) throws Exception {
        this.open();
        try {

            List<Note> list = this.database.query(matchNoteId(id));
            if (list.size() == 0) {
                throw new NoteNotFoundException();
            } else if (list.size() > 1) {
                throw new DuplicatedNoteIDException();
            } else {
                Note x = list.get(0);
                return x;
            }

        } finally {
            this.close();
        }
    }

    public int countById(final String id) {
        this.open();
        try {

            return this._countById(id);

        } finally {
            this.close();
        }
    }

    private int _countById(final String id) {
        List<Article> list = this.database.query(matchId(id));
        return list.size();
    }

    private int _countNoteById(final String id) {
        List<Note> list = this.database.query(new Predicate<Note>() {
            public boolean match(Note candidate) {
                return candidate.getId().equals(id);
            }
        });
        return list.size();
    }

    private Predicate<Article> matchId(final String id) {
        return new Predicate<Article>() {

            private static final long serialVersionUID = 1L;

            public boolean match(Article candidate) {
                return candidate.getId().equals(id);
            }
        };
    }
    
    private Predicate<Note> matchNoteId(final String id) {
        return new Predicate<Note>() {

            private static final long serialVersionUID = 1L;

            public boolean match(Note candidate) {
                return candidate.getId().equals(id);
            }
        };
    }

    public void removeAllArticles() {
        this.open();
        try {

            List<Article> list = this.database.query(Article.class);

            for (Article i : list) {
                this.database.delete(i);
            }
            this.database.commit();

        } finally {
            this.close();
        }
    }

    public void removeArticleById(final String id) {
        this.open();
        try {
            List<Article> list = this.database.query(new Predicate<Article>() {

                private static final long serialVersionUID = 1L;

                public boolean match(Article candidate) {
                    return candidate.getId().equals(id);
                }
            });

            for (Article i : list) {
                this.database.delete(i);
            }
            this.database.commit();
        } finally {
            this.close();
        }
    }

    public boolean createArticle(Article item) {
        this.open();
        try {
            item.setId(_newId(Article.class));
            boolean status = _addArticle(item);
            System.out.println("New aritlce with id: " + item.getId()
                    + " is created.");
            this.commit();
            return status;
        } finally {
            this.close();
        }
    }

    private String _newId(Class type) {
        IdManager man = this._getIdManager(type);

        int id = man.newId();
        while (this._countNoteById(Integer.toString(id)) > 0) {
            id = man.newId();
        }
        this._updateIdManager(type);
        return Integer.toString(id);
    }

    public boolean addArticle(Article item) throws DuplicatedArticleIDException {
        this.open();
        try {
            boolean status = _addArticle(item);
            if (status == false) {
                throw new DuplicatedArticleIDException();
            }
            this.commit();
            return status;
        } finally {
            this.close();
        }
    }

    private boolean _addArticle(Article item) {
        int count = this._countById(item.getId());
        if (count > 0) {
            return false;
        } else {
            this.database.store(item);
            return true;
        }
    }

    private boolean _addNote(Note item) {
        int count = this._countNoteById(item.getId());
        if (count > 0) {
            return false;
        } else {
            this.database.store(item);
            return true;
        }
    }

    public void addArticles(List<Article> list) {
        this.open();
        try {
            for (Article i : list) {
                _addArticle(i);
            }
            this.commit();
        } finally {
            this.close();
        }
    }

    public boolean createNote(Note note) {
        this.open();
        try {
            note.setId(_newId(Note.class));
            boolean status = _addNote(note);
            System.out.println("New note with id: " + note.getId()
                    + " is created.");
            this.commit();
            return status;
        } finally {
            this.close();
        }
    }
    
    private List<IdManager> getAllIdManagers(){
        this.open();
        try {

            List<IdManager> list = this.database.query(IdManager.class);
            for(int i=0; i<list.size(); i++) {
                System.out.println(list.get(i));
            }
            return list;
        }finally {
            this.close();
        }
    }

    public static void main(String[] args) throws Exception {
        TextBase db1 = new TextBase();
        // List<Article> list1 = db1.getArticleList();
        // // System.out.println(list1.size());
        // for (Article i : list1) {
        // System.out.println(i);
        //			
        // }
        // db1.removeAllArticles();
        // db1.addArticles(ModelUtil.generateArticles(10));
        //        
        // Article x = db1.getArticleById("4");
        // System.out.println(x);

//        db1.createArticle(new Article("1", "aaa", "bbb", "ccc"));
//        db1.createArticle(new Article("1", "aaa", "bbb", "ccc"));
//        db1.createArticle(new Article("1", "aaa", "bbb", "ccc"));
//        db1.createArticle(new Article("1", "aaa", "bbb", "ccc"));
//        db1.createArticle(new Article("1", "aaa", "bbb", "ccc"));
//        db1.createNote(new Note("fdsafdsf"));
//        db1.getAllIdManagers();
//        

        Article x1 = new Article("11", "haha", "hoho","hehe");
        if(db1.countById(x1.getId())==0)db1.addArticle(x1);
        System.out.println(x1);
        List<Article> list = db1.getArticleList();
        System.out.println(list.size());
        for(int i=0; i< list.size(); i++) {
            Article x = list.get(i);
//            x.setPubDate(new RSSDate());
//            db1.updateArticle(x);
            System.out.println(x);
        }
        
        System.out.println("---------------------------");
        Article x = db1.getArticleById("2");
        System.out.println("FirsT: " + x);
        x.setAuthor("zhaoffp");
        db1.updateArticle(x);
        Article y = db1.getArticleById("2");
        
        System.out.println("Second: " + y);
        
        
    }
}
